//  IDVI 1.0 source copyright 1996 Garth A. Dickie
//
//  This source is free for non-commercial use.  No warranty, etc.
//  Please acknowledge reuse by including the line:
//
//  "Based in part on IDVI 1.0 source copyright 1996 Garth A. Dickie"
//
//  in your documentation and source code.  For commercial use or
//  distribution, please contact the author.  Please also send
//  questions, comments, bug reports, or fixes.
//
//  Best Regards,
//  Garth A. Dickie
//  dickie@elastic.avid.com

package ibook.v10.idvi.split;

import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.Rectangle;

import ibook.v10.idvi.IDVI;
import ibook.v10.idvi.dvi.DVITokenizer;
import ibook.v10.idvi.dvi.DVIFormat;
import ibook.v10.idvi.dvi.DVIFormatException;
import ibook.v10.idvi.font.DVICharacter;
import ibook.v10.parameter.ParameterProcessor;
import ibook.v10.parameter.ParameterStub;

class PageInfo {
    final private static int kPageSpecialMapInitialSize = 13;

    Hashtable   pageSpecialMap = new Hashtable( kPageSpecialMapInitialSize );

    //  bounds are *not scaled*, but are separate for each scale factor
    //  because character hotspots are always aligned according to the
    //  scale factor, and this changes the bounds of the page.

    boolean empty = false;

    int[ ]  pageTop;
    int[ ]  pageLeft;
    int[ ]  pageBottom;
    int[ ]  pageRight;
    int[ ]  expansion;

    int     scaleMin;
    int     scaleCount;

    PageInfo( DVITokenizer tokenizer,
            int scaleMin, int scaleMax, String pageName, Hashtable specialMap, Vector psFileVector )
            throws DVIFormatException, IOException {
        
        this.scaleMin = scaleMin;
        scaleCount = scaleMax - scaleMin + 1;

        initializeBounds( );
        parseToEndToken( tokenizer, DVITokenizer.kTokenEndPage, pageName, specialMap, psFileVector );
        empty = pageIsEmpty( );

        // ??? a temporary fix for underlining problems:
        for( int i = 0; i < scaleCount; ++ i )
            pageBottom[ i ] += 2 * ( scaleMin + i );
    }

    private void initializeBounds( ) {
        pageTop    = new int[ scaleCount ];
        pageLeft   = new int[ scaleCount ];
        pageBottom = new int[ scaleCount ];
        pageRight  = new int[ scaleCount ];
        expansion  = new int[ scaleCount ];

        for( int i = 0; i < scaleCount; ++ i ) {
            pageTop   [ i ] = Integer.MAX_VALUE;
            pageLeft  [ i ] = Integer.MAX_VALUE;
            pageBottom[ i ] = Integer.MIN_VALUE;
            pageRight [ i ] = Integer.MIN_VALUE;
            expansion [ i ] = 0;
        }
    }

    private boolean pageIsEmpty( ) {
        for( int i = 0; i < scaleCount && ! empty; ++ i )
            if( pageTop[ i ] != Integer.MAX_VALUE
                    || pageLeft  [ i ] != Integer.MAX_VALUE
                    || pageBottom[ i ] != Integer.MAX_VALUE
                    || pageRight [ i ] != Integer.MAX_VALUE )
                return false;
        
        return true;
    }

    private void parseToEndToken( DVITokenizer tokenizer, int endToken, String pageName, Hashtable specialMap, Vector psFileVector )
            throws DVIFormatException, IOException {

        while( tokenizer.token != endToken ) {
            switch( tokenizer.token ) {
                case DVITokenizer.kTokenCharacter:          parseCharacter( tokenizer );                        break;
                case DVITokenizer.kTokenRule:               parseRule( tokenizer );                             break;
                case DVITokenizer.kTokenPSFile:             parsePSFile( tokenizer, psFileVector );             break;
                case DVITokenizer.kTokenBeginNameAnchor:    parseNameAnchor( tokenizer, pageName, specialMap ); break;
                case DVITokenizer.kTokenBeginApplet:
                                                parseApplet( tokenizer, pageName, specialMap, psFileVector );   break;
                default:                                    tokenizer.advance( );                               break;
            }
        }
        tokenizer.advance( );
    }

    private void parseCharacter( DVITokenizer tokenizer )
            throws DVIFormatException, IOException {

        DVICharacter character = tokenizer.character;

        unionRectangle(
            tokenizer.characterUnscaledX, tokenizer.characterUnscaledY,
            character.xOffset, character.yOffset,
            character.width, character.height );

        tokenizer.advance( );
    }

    private void parseRule( DVITokenizer tokenizer )
            throws DVIFormatException, IOException {

        unionRectangle(
            tokenizer.ruleUnscaledX, tokenizer.ruleUnscaledY,
            0, tokenizer.ruleUnscaledHeight,
            tokenizer.ruleUnscaledWidth, tokenizer.ruleUnscaledHeight );
        
        tokenizer.advance( );
    }

    private void parsePSFile( DVITokenizer tokenizer, Vector psFileVector )
            throws DVIFormatException, IOException {

        unionRectangle(
            tokenizer.psFileUnscaledX, tokenizer.psFileUnscaledY,
            0, tokenizer.psFileUnscaledHeight,
            tokenizer.psFileUnscaledWidth, tokenizer.psFileUnscaledHeight );
        
        psFileVector.addElement( new PSFileInfo( tokenizer ));
        
        tokenizer.advance( );
    }

    private void parseNameAnchor( DVITokenizer tokenizer, String pageName, Hashtable specialMap )
            throws DVIFormatException, IOException {

        String prefix = IDVI.kIDVIURLPrefix + IDVI.kToggleURLPrefix;

        if( ! prefix.regionMatches( true, 0, tokenizer.name, 0, prefix.length( ))) {
            String key = "html:<A href=\"#" + tokenizer.name + "\">";
            String element = "html:<A href=\"" + pageName + "#" + tokenizer.name + "\">";

            pageSpecialMap.put( key, key );
            specialMap.put( key, element );
        }
        
        tokenizer.advance( );
    }

    //  UGLY!  But I don't have time do the right thing, which would involve passing a bounds
    //  DVIRectangle object around, having things modify it, etc.
    //
    //  Even better would be to have PageInfo use the PageParser to create a Block structure,
    //  then create ViewPanels for each scale factor, and ask them what their bounds are.
    //  But this would be much slower, I think.

    private void parseApplet( DVITokenizer tokenizer, String pageName, Hashtable specialMap, Vector psFileVector )
            throws DVIFormatException, IOException {

        ParameterStub stub = tokenizer.appletParameter;
        tokenizer.advance( );

        //  We do a little switch here.  We save the bounds of the actual page, and then
        //  set up new bounds arrays.  The text inside the applet tag is parsed, giving us
        //  some bounds.  Then we save these bounds, and put the original bounds back.

        int[ ] saveTop       = pageTop;
        int[ ] saveLeft      = pageLeft;
        int[ ] saveBottom    = pageBottom;
        int[ ] saveRight     = pageRight;
        int[ ] saveExpansion = expansion;

        initializeBounds( );
        parseToEndToken( tokenizer, DVITokenizer.kTokenEndApplet, pageName, specialMap, psFileVector );
        boolean appletEmpty = pageIsEmpty( );

        int[ ] newTop       = pageTop;
        int[ ] newLeft      = pageLeft;
        int[ ] newBottom    = pageBottom;
        int[ ] newRight     = pageRight;
        int[ ] newExpansion = expansion;

        pageTop       = saveTop;
        pageLeft      = saveLeft;
        pageBottom    = saveBottom;
        pageRight     = saveRight;
        expansion = saveExpansion;

        //  Now we have bounds (in newTop, etc) for the text inside the applet tag,
        //  and have to decide how this will effect the page bounds.

        if( ! appletEmpty ) {
            //  We retrieve the width and height parameters from the applet tag,
            //  if they are present.  If one is missing, the applet takes that
            //  value from the bounds of the text which it enclosed.

            int fixedHeight = 0;
            boolean hasFixedHeight = false;
            String heightString = stub.getParameter( "height" );
            if( heightString != null )
                try {
                    fixedHeight = Integer.parseInt( heightString );
                    hasFixedHeight = true;
                } catch( NumberFormatException e ) {
                }
            
            int fixedWidth = 0;
            boolean hasFixedWidth = false;
            String widthString = stub.getParameter( "width" );
            if( widthString != null )
                try {
                    fixedWidth = Integer.parseInt( widthString );
                    hasFixedWidth = true;
                } catch( NumberFormatException e ) {
                }

            for( int i = 0; i < scaleCount; ++ i ) {
                //  We will add a fudge factor (one pixel after scaling), because
                //  this is only an approximation of where the applet actually ends
                //  up on the page.  This is done by expanding the applet bounds
                //  by scale unscaled pixels in every direction.

                int scale = i + scaleMin;

                //  If the applet has a fixed width, then the left and right bounds of
                //  the applet are found by centering the applet over the text which
                //  was inside the applet tag.  Otherwise, the applet has the same left
                //  and right bounds as the text inside the applet tag.

                int appletLeft;
                int appletRight;
                if( hasFixedWidth ) {
                    int appletCenter = ( newLeft[ i ] + newRight[ i ] ) / 2;
                    appletLeft = appletCenter - scale * fixedWidth / 2 - scale;
                    appletRight = appletLeft + scale * fixedWidth + scale;
                } else {
                    appletLeft = newLeft[ i ] - scale;
                    appletRight = newRight[ i ] + scale;
                }

                //  Whether the applet has a fixed height or not, the top and bottom
                //  bounds come from the text it encloses.  However, with a fixed height
                //  the page is expanded (or contracted) to accomodate the applet.

                int appletTop = newTop[ i ] - scale;
                int appletBottom = newBottom[ i ] + scale;
                int appletExpansion = 0;
                if( hasFixedHeight ) {
                    int scaledTop    = ( newTop   [ i ] + scale - 1 ) / scale;
                    int scaledBottom = ( newBottom[ i ] + scale - 1 ) / scale;
                    int scaledHeight = scaledBottom - scaledTop;

                    appletExpansion = scale * fixedHeight - scaledHeight;
                }

                //  Now we record these bounds in the page bounds.

                if( appletTop    < pageTop   [ i ] ) pageTop   [ i ] = appletTop;
                if( appletLeft   < pageLeft  [ i ] ) pageLeft  [ i ] = appletLeft;
                if( appletBottom < pageBottom[ i ] ) pageBottom[ i ] = appletBottom;
                if( appletRight  > pageRight [ i ] ) pageRight [ i ] = appletRight;

                expansion[ i ] += appletExpansion;
            }
        }
    }

    private void unionRectangle( int x, int y, int xOffset, int yOffset, int width, int height ) {
        for( int scaleIndex = 0; scaleIndex < scaleCount; ++ scaleIndex ) {
            int scale = scaleIndex + scaleMin;

            int adjustedX = x - x % scale;
            int adjustedY = y - y % scale;

            int left   = adjustedX - xOffset;
            int top    = adjustedY - yOffset;
            int right  = left + width;
            int bottom = top + height;

            if( top    < pageTop   [ scaleIndex ] ) pageTop   [ scaleIndex ] = top;
            if( left   < pageLeft  [ scaleIndex ] ) pageLeft  [ scaleIndex ] = left;
            if( bottom > pageBottom[ scaleIndex ] ) pageBottom[ scaleIndex ] = bottom;
            if( right  > pageRight [ scaleIndex ] ) pageRight [ scaleIndex ] = right;
        }
    }
}

class PSFileInfo {
    String              prefix;
    int                 width;
    int                 height;
    Hashtable           parameterHashtable;
    ParameterProcessor  parameter;

    PSFileInfo( DVITokenizer tokenizer ) {
        prefix              = tokenizer.psFilePrefix;
        width               = tokenizer.psFileUnscaledWidth;
        height              = tokenizer.psFileUnscaledHeight;
        parameterHashtable  = tokenizer.psFileParameterHashtable;
        parameter           = tokenizer.psFileParameter;
    }
}
